/*-----------------------------------------------------------------------------
 * 
 *
 *-----------------------------------------------------------------------------
 */

#include "server/database/include/stock_database.h"

avl_tree        *stock_database;
stock_t         *stock_database_list;
pthread_mutex_t  stock_database_mutex;
pthread_mutex_t  stock_database_list_mutex;

#define STOCK_MAX 1000

static stock_t stock_chunk[STOCK_MAX];
static int     stock_chunk_index = 0;

static int
stock_compare(void *s1, void *s2, void *ctx)
{
    stock_t *stock1 = (stock_t *) s1;
    stock_t *stock2 = (stock_t *) s2;
    int      insensitive = (int) ctx;

    if (insensitive) {
        return strncasecmp(stock1->ticker, stock2->ticker, STOCK_TICKER_LEN);
    } else {
        return strncmp(stock1->ticker, stock2->ticker, STOCK_TICKER_LEN);
    }
    
}


static void
stock_free(void *s) 
{
    stock_t *stock = (stock_t *) s;
    free(stock);
}


int
stock_database_init()
{
    memset(stock_chunk, 0, sizeof(stock_t) * STOCK_MAX);

    stock_database = avl_new(stock_compare, stock_free, AVL_TREE_INTRUSIVE);

    if (stock_database == NULL) {
        return -1;
    }

    if (pthread_mutex_init(&stock_database_mutex, NULL) != 0) {
        return -1;
    }

    stock_database_list = NULL;

    if (pthread_mutex_init(&stock_database_list_mutex, NULL) != 0) {
        return -1;
    }

    return 0;
}

int
stock_database_cleanup()
{

    return 0;
}

static stock_t *
stock_chunk_alloc(char *ticker) 
{
    stock_t *stock = NULL;

    if (stock_chunk_index < STOCK_MAX) {
        stock = &stock_chunk[stock_chunk_index++];
    }

    return stock;
}

stock_t *
stock_create(char *ticker)
{
    stock_t *stock = stock_chunk_alloc(ticker);

    if (stock == NULL) {
        return NULL;
    }

    memcpy(stock->ticker, ticker, STOCK_TICKER_LEN);
    stock->temp_bin_min_price = 0xFFFFFFFF;
    stock->temp_bin_max_price = 0x0;

    stock = (stock_t *) avl_insert(stock_database, stock, NULL);

    if (stock == NULL) {
        return NULL;
    }

    STOCK_DATABASE_LIST_LOCK();
    stock->next = stock_database_list;
    stock_database_list = stock;
    STOCK_DATABASE_LIST_UNLOCK();

    return stock;
}

void 
stock_delete(stock_t *stock) 
{

}

stock_t *
stock_search(char *ticker, int insensitive)
{
    stock_t *stock = NULL;
    stock_t  temp;

    memcpy(temp.ticker, ticker, STOCK_TICKER_LEN);
    
    stock = (stock_t *) avl_lookup(stock_database, &temp, (void*) insensitive);

    return stock;
}

static void
stock_temp_bin_clear(stock_t *stock)
{
    stock->temp_bin_count = 0;
    stock->temp_bin_min_price = 0xFFFFFFFF;
    stock->temp_bin_max_price = 0x0;
    stock->temp_bin_sum_price = 0;
    memset(stock->temp_bin, 0, STOCK_MAX_TEMP);
}

static stock_update *
stock_temp_bin_insert(stock_t *stock, stock_update *update)
{
    int temp_index = stock->temp_bin_count++;

    if (temp_index >= STOCK_MAX_TEMP) {
        stock->temp_bin_overflow++;
        return NULL;
    }

    memcpy(&stock->temp_bin[temp_index], &update->data, sizeof(stock_data));
    stock->temp_bin_sum_price += update->data.price;

    if (update->data.price < stock->temp_bin_min_price) {
        stock->temp_bin_min_price = update->data.price;
    }

    if (update->data.price >= stock->temp_bin_max_price) {
        stock->temp_bin_max_price = update->data.price;
    }

    return update;
}

stock_update *
stock_database_insert(stock_update *update) 
{
    stock_t  *stock = NULL;
    int       current;
    int       delta;
    uint32_t  timestamp;
    uint32_t  average;

    stock = stock_search(update->ticker, 0);

    if (stock == NULL) {
        /*
         *
         */
        stock = stock_create(update->ticker);
        if (stock == NULL) {
            return NULL;
        }

        stock->current_timestamp = update->data.timestamp;
        stock_temp_bin_insert(stock, update);

    } else {

        timestamp = stock->current_timestamp;
        delta = update->data.timestamp - timestamp;
    
        /*
         * Ignore
         */
        if (delta < -1) {
            stock->dropped_unsynchronized++;
            return NULL;
    
        } else if (delta >= -1 && delta <= 1) {
           /*
            * Stash update in temp bin
            */
            stock_temp_bin_insert(stock, update);
    
        } else if (delta > 1) {
            /*
             * Flush temp bin and start next bin
             */
            average = stock->temp_bin_sum_price / stock->temp_bin_count;
            current = stock->data_count++;
            stock->data[current].timestamp = timestamp;
            stock->data[current].price = average;
    
            stock_temp_bin_clear(stock);
            stock_temp_bin_insert(stock, update);
    
            stock->current_timestamp = update->data.timestamp;
        }
    }

    if (stock != NULL) {
        /*
         * TODO: atomic_inc
         */
        stock->temp_updates++;
    }

    return update;
}


int
stock_database_filter(stock_update *update)
{
    uint32_t update_time = update->data.timestamp;
    uint32_t local_time = stock_gettimeofday();;
    int      delta = local_time - update_time;
    uint32_t hour, minute, second;

    /*
     * If the update timestamp is not within close range of the server time, we
     * will drop the update.  The clients and servers are supposed to be ntp 
     * synchronized.
     */
    if (delta < -3 || delta > 1) {;
        //stock_server_update_stats.dropped_unsynchronized++;
        return STOCK_DATABASE_FILTER_UNSYNC;
    }

    //XXX
    return 0;

    /*
     * If the update timestamp is not within U.S. stock market hours (9:30AM to
     * 1:00PM EDT, or 1:30PM to 5PM GMT) then drop the update
     */
    hour =   (update_time % 86400) / 3600;
    minute = (update_time % 3600) / 60;
    second = (update_time % 60);

    if (hour >= 11 && minute >= 30 &&
        hour <= 16 && minute <= 59 && second <= 59) {
        return 0;
    }

    return -1;
}


uint32_t 
stock_last_price(stock_t *stock)
{
    if (stock->temp_bin_count > 0) {
        return (stock->temp_bin_sum_price / stock->temp_bin_count);
    } else {
        return stock->data[stock->data_count - 1].timestamp;
    }
}


uint32_t
stock_last_timestamp(stock_t *stock)
{
    return stock->current_timestamp;
}
